<?php
	require_once('Zend/Loader.php');
	require_once('SDB.php');
	require_once('S3.php');
	
	/**
	 * MailProcessor
	 *
	 * used by the mail processing script to process media in
	 * messages sent by registered users.  saves media to S3
	 * storage and stores information in a simpledb.
	 *
	 * @author David Ivins
	 */
	class MailProcessor
	{
		private $_config;
		private $_logger;
		private $_sdb;
		private $_s3;
		
		/** 
		 * __construct
		 */
		public function __construct()
		{ /*{{{*/
			# register zend autoloader for zend classes
			Zend_Loader::registerAutoLoad();

			# load the application configuration
			$this->_config = new Zend_Config_Ini(
				'/var/www/mobloggin/config/settings.ini', 'live');
			Zend_Registry::set('config', $this->_config);

			# create the application logger
			$mode = (file_exists($this->_config->path->logs->mail->file)) ? 
				'a' : 'w';
			$this->_logger = new Zend_Log(new Zend_Log_Writer_Stream(
				$this->_config->path->logs->mail->file), $mode);
			Zend_Registry::set('logger', $this->_logger);
			
			# change permissions of file if necessary
			if ($mode == 'w')
				chmod($this->_config->path->logs->mail->file, 0666);

			# get aws keys
			$aws_public = exec('getawskey public');
			$aws_private = exec('getawskey private');

			# create simpledb interaction object and store it in the registry
			$this->_sdb = new SDB($aws_public, $aws_private);
			Zend_Registry::set('sdb', $this->_sdb);

			# create s3 interaction object and store it in the registry
			$this->_s3 = new S3($aws_public, $aws_private);
			Zend_Registry::set('s3', $this->_s3);
		} /*}}}*/

		/** 
		 * getConfiguration
		 */
		public function getConfiguration()
		{ /*{{{*/
			# return the current configuration
			return $this->_config;
		} /*}}}*/

		/** 
		 * log
		 * 
		 * Used to log information to a log file.  Not used by this
		 * specific file.
		 *
		 * **IMPORTANT** : This is used by the mail processing script.
		 * It is not useless. Do not remove it. 
		 *
		 * @param string $message the message to log
		 * @param string $type (optional) the type of log message
		 */
		public function log($message, $type = null)
		{ /*{{{*/
			# get Zend_Log constant for given type
			switch ($type)
			{
				case "error":
					$zend_type = Zend_Log::ERR;
					break;
				case "debug":
					$zend_type = Zend_Log::DEBUG;
					break;
				default:
					$zend_type = Zend_Log::WARN;
			}
			
			# log the give message
			$this->_logger->log($message, $zend_type);
		} /*}}}*/
		
		/**
		 * sendFailureReply
		 */
		public function sendFailureEmails(Zend_Mail_MessageFromString $msg)
		{ /*{{{*/
			# get info for failure email
			$headers = $msg->getHeaders();
			$from = $this->getAddrFromEmail($headers['from']);
			$msg = 'Your media has failed to upload. Mobloggin '
				. 'administrators have been notified and will correct the issue. '
				. 'Thank you for your patience.';
			
			# construct and send failure email
			$reply = new Zend_Mail();
			$reply->setFrom('noreply@moblogg.in', 'Mobloggin Website');
			$reply->addTo($from);
			$reply->setSubject($msg);
			$reply->setBodyText($msg);
			$reply->send();
		} /*}}}*/
		
		/** 
		 * processMessage
		 * @throws MailProcessorException
		 */
		public function processMessage(Zend_Mail_MessageFromString $msg)
		{ /*{{{*/
			# get information for this message
			$headers = $msg->getHeaders();
			$subject = $headers['subject'];
			$user_id = $this->getUserId($headers['from']); 
			
			# check if there is an attachment (media)
			if ($msg->isMultipart())
			{
				# count the number of parts in this message
				$num_parts = $msg->countParts();
				$count = 0;

				# go through each part contained in the message
				for ($i = 1; $i <= $num_parts; $i++)
				{
					# get information for this part
					$part = $msg->getPart($i);
					$part_headers = $part->getHeaders();
					$part_headers['Content-Type'] = $part_headers['content-type'];
					$content_type_arr = split(';', $part_headers['content-type']);
					$content_type = trim($content_type_arr[0]);

					# check if this part has a valid content-type (ie: one we handle)
					if ($this->contentTypeIsValid($content_type))
					{
						# generate storage and database information
						$content = $part->getContent();
						$dates = CommonGenerator::generateCurrentIso8601Dates();
						$url = CommonGenerator::generateUniqueMediaUrlString($subject);

						# set up attributes for record
						$attributes = array(
							'title' => $subject, 
							'url' => $url, 
							'user_id' => $user_id, 
							'date' => $dates['normal'], 
							'date_inverted' => $dates['inverted'], 
						);
						
						$binary_info = array(
							'headers' => $part_headers,
							'access' => S3::ACL_PUBLIC_READ
						);
						
						$media = new Media();
						$media->setAttributes($attributes);
						$media->setBinaryInfo(base64_decode($content), $binary_info);
						$media->save($count);
						$count++;
					}
					else
					{
						# no exception for this.  all messages will have a text/plain content-type
						# for the text content of the message and some carriers attach gifs or
						# other items containing their logos as well.  just ignore them.
						$this->_logger->log(get_class($this) . ':: Ignoring unrecognized '
							. "content-type '$content_type' part of current message.", Zend_Log::DEBUG);
					}
				}
			}
			else
			{
				# no attachments! throw an exception
				$ex_msg = "A message from user {$headers['from']} failed to" 
					. ' upload! The Message did not contain any attachments.  Data dump to follow...';
				throw new MailProcessorException($ex_msg, 1);
			}
		} /*}}}*/

		/** 
		 * getUserId
		 * @throws Amazon_SimpleDB_Exception
		 * @throws MailProcessorException
		 */
		private function getUserId($from)
		{ /*{{{*/
			$domain = $this->_config->domain->name->users;
			$from_prefix = $this->getUsernameFromEmail($from);
			$from_addr = $this->getAddrFromEmail($from);

			# query the user database for either a phone 
			# number of an email address matching the 'from'
			# field in the message received
			if (is_numeric($from_prefix))
			{
				$query = "['phone' = '$from_prefix']";
				$response = $this->_sdb->query($domain, $query);
			}
			else // else it's an email address
			{
				$query = "['email' = '$from_addr']";
				$response = $this->_sdb->query($domain, $query);
			}

			# get query results and pull out the user id
			if ($response->isSetQueryResult()) 
			{ 
				$result = $response->getQueryResult();
				$item_names  =  $result->getItemName();
				
				# check if the user id was found
				if (isset($item_names[0]))
				{
					$user_id = $item_names[0];
				}
				else
				{
					# no user id found! throw an exception
					$ex_msg = get_class($this) . '::Failed upload attempt from unregistered or unrecognized'
						. " user {$headers['from']}. Data dump to follow...";
					throw new MailProcessorException($ex_msg, 0);
				}
			}
			
			return $user_id;
		} /*}}}*/

		/** 
		 * getUsernameFromEmail
		 */
		private function getUsernameFromEmail($raw_from)
		{ /*{{{*/
			# pull out phone number digits from address
			if (strpos($raw_from, '<') === false)
			{
				$from = substr($raw_from, 0, strpos($raw_from, '@'));
			}
			else
			{
				preg_match('/(<)(?<from>.*)(@)/', $raw_from, $out);
				$from = $out['from'];
			}

			return trim($from);
		} /*}}}*/

		/** 
		 * getAddreFromEmail
		 */
		private function getAddrFromEmail($raw_from)
		{ /*{{{*/
			# pull out email address from address brackets
			if (strpos($raw_from, '<') === false)
			{
				$from = $raw_from;
			}
			else
			{
				preg_match('/(<)(?<from>.*)(>)/', $raw_from, $out);
				$from = $out['from'];
			}
			
			return trim($from);
		} /*}}}*/

		/** 
		 * contentTypeIsValid
		 */
		private function contentTypeIsValid($content_type)
		{ /*{{{*/
			# check if content-type is a valid type in the config file
			$valid_types = split(',', $this->_config->content_types->valid);
			return in_array($content_type, $valid_types);
		}	/*}}}*/

	}
